package org.changs.media;

import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.support.annotation.IntDef;

import com.google.gson.JsonObject;

import org.changs.aplug.utils.ConstUtils;
import org.changs.aplug.utils.FileUtils;
import org.changs.servlet.AplugClient;
import org.changs.servlet.CommunicationConfig;
import org.changs.servlet.IOUtils;
import org.changs.servlet.SenderFileCallback;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Singleton;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;

/**
 * Created by yincs on 2017/9/5.
 */

/**
 * 媒体文件的相关操作
 * 媒体文件的名称格式 yyyyMMddHHmmss_[f\b\l\r][l\n].[mp4\jpg]
 * 解释
 * [0~14):日期 ;
 * [15]：f前、b后、l左、r右
 * [16]:l锁定n未锁定
 */

@Singleton
public class MediaManager implements IMediaManager {
    public static final int TYPE_VEHICLE = 0;
    public static final int TYPE_PHONE = 1;

    @IntDef({TYPE_VEHICLE, TYPE_PHONE})
    public @interface Type {
    }

    public static final SimpleDateFormat RECORD_SDF = new SimpleDateFormat("yyyyMMddHHmmss");
    public static final SimpleDateFormat USER_SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


    private final Context mContext;
    private final List<MediaFile> mMediaFiles = new ArrayList<>();
    private List<Callback> mCallback = new ArrayList<>();
    private final Handler mHandler = new Handler();
    private @Type int mMediaManagerType;
    private final File mVideoDir;
    private final File mPictureDir;

    @Inject
    public MediaManager(Context context, @MediaManagerType @Type int type) {//文件的根目录
        this.mContext = context;
        this.mMediaManagerType = type;

        if (type == TYPE_VEHICLE) {
            this.mVideoDir = MediaManager.getVehicleVideoDir(context);
            this.mPictureDir = MediaManager.getVehiclePictureDir(context);
        } else {
            this.mVideoDir = MediaManager.getPhoneVideoDir(context);
            this.mPictureDir = MediaManager.getPhonePictureDir(context);
        }
    }

    @Override
    public List<MediaFile> getMedia() {
        return mMediaFiles;
    }

    @Override
    public List<MediaFile> getMedia(MediaType type, MediaSite site) {
        List<MediaFile> result = new ArrayList<>();
        for (MediaFile mediaFile : mMediaFiles) {
            if (mediaFile.getType().equals(type)) {
                if (site == MediaSite.ALL || site == mediaFile.getSite()) {
                    result.add(mediaFile);
                }
            }
        }
        return result;
    }

    @Override
    public void delete(MediaFile mediaFile) {
        Observable
                .fromCallable(() -> FileUtils.deleteFile(mediaFile.getAbsolutePath()))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(aBoolean -> {
                    Timber.d("delete MediaFile = " + aBoolean);
                    if (aBoolean) {
                        mMediaFiles.remove(mediaFile);
                        notifyDelete(mediaFile);
                        if (mediaFile.getThumb() != null) {
                            mediaFile.getThumb().recycle();
                        }
                    }
                });
    }

    @Override
    public boolean upload(String remoteIp, MediaFile mediaFile) {
        if (mMediaManagerType != TYPE_VEHICLE) return false;
        if (remoteIp == null) {
            return false;
        }
        if (MediaState.UPLOADING == mediaFile.getState()) return false;
        Observable
                .fromCallable(() -> {
                    JsonObject message = new JsonObject();
                    message.addProperty(IOUtils.EXTRA_FILE_RELATIVE_PATH, mediaFile.getRelativePath());
                    AplugClient.send(remoteIp, CommunicationConfig.PHONE_SERVER_PORT,
                            message, new File(mediaFile.getAbsolutePath()),
                            new MediaManager.MySenderFileCallback(mediaFile));
                    return true;
                })
                .subscribeOn(Schedulers.io())
                .subscribe();

        mediaFile.setState(MediaState.UPLOADING);
        return true;
    }

    @Override
    public void reload() {
        Observable
                .fromCallable(() -> {
                    List<MediaFile> mediaFiles = new ArrayList<>();
                    final File[] videoFile = mVideoDir.listFiles(pathname -> pathname.getName().endsWith(".mp4"));
                    if (videoFile != null) {
                        for (File file : videoFile) {
                            final MediaFile mediaFile = MediaFile.parseFile(file, MediaType.VIDEO);
                            if (mediaFile == null) continue;
                            mediaFiles.add(mediaFile);
                        }
                    }
                    final File[] pictureFile = mPictureDir.listFiles(pathname -> pathname.getName().endsWith(".jpg"));
                    if (pictureFile != null) {
                        for (File file : pictureFile) {
                            final MediaFile mediaFile = MediaFile.parseFile(file, MediaType.PICTURE);
                            if (mediaFile == null) continue;
                            mediaFiles.add(mediaFile);
                        }
                    }
                    return mediaFiles;
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(result -> {
                    for (int i = 0; i < result.size(); i++) {
                        int index = mMediaFiles.indexOf(result.get(i));
                        if (index != -1) {
                            result.remove(i);
                            result.add(i, mMediaFiles.get(index));
                        }
                    }
                    mMediaFiles.clear();
                    mMediaFiles.addAll(result);
                    notifyReload();
                });
    }

    @Override
    public void lock(MediaFile mediaFile) {
        if (mediaFile.isLock()) return;
        File file = new File(mediaFile.getAbsolutePath());
        if (!file.exists()) return;
        StringBuilder sb = new StringBuilder(file.getName());
        String name = sb.replace(16, 17, "l").toString();
        Timber.d("lock_name = " + name);
        File dest = new File(file.getParent(), name);
        file.renameTo(dest);
        mediaFile.setAbsolutePath(dest.getAbsolutePath());
        mediaFile.setName(dest.getName());
    }

    @Override
    public void unLock(MediaFile mediaFile) {
        if (mediaFile.isLock()) return;
        File file = new File(mediaFile.getAbsolutePath());
        if (!file.exists()) return;
        StringBuilder sb = new StringBuilder(file.getName());
        String name = sb.replace(16, 17, "n").toString();
        Timber.d("lock_name = " + name);
        File dest = new File(file.getParent(), name);
        file.renameTo(dest);
        mediaFile.setAbsolutePath(dest.getAbsolutePath());
        mediaFile.setName(dest.getName());
    }

    private void notifyReload() {
        for (Callback callback : mCallback) {
            callback.reload();
        }
    }

    private void notifyDelete(MediaFile mediaFile) {
        for (Callback callback : mCallback) {
            callback.delete(mediaFile);
        }
    }

    private void notifyUpload(MediaFile mediaFile) {
        for (Callback callback : mCallback) {
            callback.upload(mediaFile);
        }
    }

    @Override
    public void registerCallback(Callback callback) {
        mCallback.add(callback);
    }

    @Override
    public void unregisterCallback(Callback callback) {
        mCallback.remove(callback);
    }

    private class MySenderFileCallback implements SenderFileCallback {

        final MediaFile mediaFile;

        private MySenderFileCallback(MediaFile mediaFile) {
            this.mediaFile = mediaFile;
        }

        @Override
        public void onNext(int code, String msg) {
        }

        @Override
        public void onProgress(int progress) {
        }

        @Override
        public void onError(int code, String msg) {
            mHandler.post(() -> {
                mediaFile.setState(MediaState.DEFAULT);
                notifyUpload(mediaFile);
            });
        }

        @Override
        public void onSuccess() {
            mHandler.post(() -> {
                mediaFile.setState(MediaState.UPLOADED);
                notifyUpload(mediaFile);
            });
        }

        @Override
        public void onComplete() {
        }
    }


    public static File getVehicleMediaDir(Context context) {
        return new File(Environment.getExternalStorageDirectory(), "app-s3-record");
    }

    public static File getVehicleVideoDir(Context context) {
        return new File(getVehicleMediaDir(context), "video");
    }

    public static File getVehiclePictureDir(Context context) {
        return new File(getVehicleMediaDir(context), "picture");
    }

    public static File getPhoneMediaDir(Context context) {
        return new File(Environment.getExternalStorageDirectory(), "app-phone");
    }

    public static File getPhoneVideoDir(Context context) {
        return new File(getPhoneMediaDir(context), "video");
    }

    public static File getPhonePictureDir(Context context) {
        return new File(getPhoneMediaDir(context), "picture");
    }

    public static File createVehicleMediaFile(Context context, String cameraId) {
        File vehicleVideoDir = getVehicleVideoDir(context);
        checkSpace(vehicleVideoDir, 20);
        String fileName = String.format("%s_%sn.mp4", RECORD_SDF.format(new Date()),
                getSuitByCameraId(cameraId));
        return new File(vehicleVideoDir, fileName);
    }

    public static File createVehiclePictureFile(Context context, String cameraId) {
        File vehicleVideoDir = getVehiclePictureDir(context);
        String fileName = String.format("%s_%sn.jpg", RECORD_SDF.format(new Date()),
                getSuitByCameraId(cameraId));
        return new File(vehicleVideoDir, fileName);
    }


    private static void checkSpace(File dir, int needMB) {
        long freeSpace = dir.getFreeSpace();
        Timber.d("freeSpace = " + freeSpace);
        int m = (int) (freeSpace / ConstUtils.MB);

        if (m < needMB) {
            Timber.d("getVideoFilePath: 剩余空间不足100M");
            String[] fileNames = dir.list();
            if (fileNames == null) return;
            Arrays.sort(fileNames);
            List<String> fileNameList = Arrays.asList(fileNames);
            Iterator<String> iterator = fileNameList.iterator();
            long deleteSize = 0;
            boolean isEnough = false;
            while (iterator.hasNext()) {
                String fileName = iterator.next();
                if (MediaFile.isLock(fileName)) continue;
                Timber.d("fileName = " + fileName);
                if (deleteSize < needMB * ConstUtils.MB) {
                    File file = new File(dir, fileName);
                    deleteSize += file.getFreeSpace();
                    boolean deleteFile = FileUtils.deleteFile(file);
                    Timber.d("删除文件：" + deleteFile);
                } else {
                    isEnough = true;
                    break;
                }
            }
            if (!isEnough && fileNameList.size() > 0) {
                for (String fileName : fileNameList) {
                    if (deleteSize < needMB * ConstUtils.MB) {
                        File file = new File(dir, fileName);
                        deleteSize += file.getFreeSpace();
                        boolean deleteFile = FileUtils.deleteFile(file);
                        Timber.d("删除锁定的文件：" + deleteFile);
                    } else {
                        break;
                    }
                }
            }
        }
    }

    private static String getSuitByCameraId(String cameraId) {
        switch (cameraId) {
            case "0":
                return "f";
            case "1":
                return "b";
            case "2":
                return "l";
            case "3":
                return "r";
            default:
                return "f";
        }
    }


}
